package com.dimples.dd.system.service.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.nacos.shaded.com.google.common.annotations.VisibleForTesting;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.dimples.dd.common.constant.DDConstant;
import com.dimples.dd.common.enums.CommonStatusEnum;
import com.dimples.dd.common.model.Option;
import com.dimples.dd.system.convert.DeptConverter;
import com.dimples.dd.system.core.dal.redis.RedisKeyConstants;
import com.dimples.dd.system.mapper.SystemDeptMapper;
import com.dimples.dd.system.model.bo.DeptBO;
import com.dimples.dd.system.model.entity.SystemDeptDO;
import com.dimples.dd.system.model.form.DeptForm;
import com.dimples.dd.system.model.query.DeptPageQuery;
import com.dimples.dd.system.model.vo.DeptTreeVO;
import com.dimples.dd.system.service.SystemDeptService;
import lombok.RequiredArgsConstructor;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.*;
import java.util.stream.Collectors;

import static com.dimples.dd.common.exception.BizExceptionUtil.exception;
import static com.dimples.dd.common.exception.ErrorCode.*;

@Service
@RequiredArgsConstructor
public class SystemDeptServiceImpl extends ServiceImpl<SystemDeptMapper, SystemDeptDO> implements SystemDeptService {

    private final DeptConverter deptConverter;

    @Override
    public List<SystemDeptDO> selecttDeptList(Collection<Long> ids) {
        if (CollUtil.isEmpty(ids)) {
            return Collections.emptyList();
        }
        return this.baseMapper.selectBatchIds(ids);
    }

    @Override
    public void validateDeptList(Collection<Long> ids) {
        if (CollUtil.isEmpty(ids)) {
            return;
        }
        // 获得科室信息
        Map<Long, SystemDeptDO> deptMap = selectDeptMap(ids);
        // 校验
        ids.forEach(id -> {
            SystemDeptDO dept = deptMap.get(id);
            if (dept == null) {
                throw exception(DEPT_NOT_FOUND);
            }
            if (ObjectUtil.notEqual(CommonStatusEnum.ENABLE.getValue(), dept.getStatus().intValue())) {
                throw exception(DEPT_NOT_ENABLE, dept.getName());
            }
        });
    }

    @Override
    public List<DeptTreeVO> listDepartments(DeptPageQuery queryParams) {
        List<DeptBO> deptList = this.baseMapper.selectList(queryParams);

        Set<Long> deptIds = deptList.stream().map(DeptBO::getId).collect(Collectors.toSet());
        Set<Long> parentIds = deptList.stream().map(DeptBO::getParentId).collect(Collectors.toSet());
        List<Long> rootIds = CollUtil.subtractToList(parentIds, deptIds);

        List<DeptTreeVO> list = CollUtil.newArrayList();
        for (Long rootId : rootIds) {
            list.addAll(recurDeptList(rootId, deptList));
        }
        return list;
    }

    /**
     * 递归获取部门列表
     *
     * @param parentId 父节点
     * @param deptList 列表数据
     * @return 树结构数据
     */
    public List<DeptTreeVO> recurDeptList(Long parentId, List<DeptBO> deptList) {
        return deptList.stream()
                .filter(dept -> dept.getParentId().equals(parentId))
                .map(dept -> {
                    DeptTreeVO deptVO = deptConverter.entity2Vo(dept);
                    List<DeptTreeVO> children = recurDeptList(dept.getId(), deptList);
                    deptVO.setChildren(children);
                    return deptVO;
                }).collect(Collectors.toList());
    }

    @Override
    public List<Option<Long>> listDeptOptions() {
        List<SystemDeptDO> deptList = this.list(
                new LambdaQueryWrapper<SystemDeptDO>()
                        .select(SystemDeptDO::getId, SystemDeptDO::getName, SystemDeptDO::getParentId)
                        .eq(SystemDeptDO::getStatus, CommonStatusEnum.ENABLE.getValue())
                        .orderByAsc(SystemDeptDO::getSort)
        );

        Set<Long> parentIds = deptList.stream().map(SystemDeptDO::getParentId).collect(Collectors.toSet());
        Set<Long> deptIds = deptList.stream().map(SystemDeptDO::getId).collect(Collectors.toSet());
        List<Long> rootIds = CollectionUtil.subtractToList(parentIds, deptIds);

        List<Option<Long>> list = CollUtil.newArrayList();
        for (Long rootId : rootIds) {
            list.addAll(recurDeptTreeOptions(rootId, deptList));
        }
        // 添加顶层父级, id为0
        Option<Long> rootOption = new Option<>(DDConstant.ROOT_NODE_ID, "顶级部门");
        rootOption.setChildren(list);
        return CollUtil.newArrayList(rootOption);
    }

    /**
     * 递归获取部门树选项
     *
     * @param parentId 父级ID
     * @param deptList 部门列表
     * @return 部门下拉列表
     */
    public static List<Option<Long>> recurDeptTreeOptions(long parentId, List<SystemDeptDO> deptList) {
        return CollectionUtil.emptyIfNull(deptList).stream()
                .filter(dept -> dept.getParentId().equals(parentId))
                .map(dept -> {
                    Option<Long> option = new Option<>(dept.getId(), dept.getName());
                    List<Option<Long>> children = recurDeptTreeOptions(dept.getId(), deptList);
                    if (CollectionUtil.isNotEmpty(children)) {
                        option.setChildren(children);
                    }
                    return option;
                })
                .collect(Collectors.toList());
    }

    @Override
    public DeptForm selectDeptForm(Long deptId) {
        SystemDeptDO entity = this.getOne(new LambdaQueryWrapper<SystemDeptDO>()
                .eq(SystemDeptDO::getId, deptId)
                .select(
                        SystemDeptDO::getId,
                        SystemDeptDO::getName,
                        SystemDeptDO::getParentId,
                        SystemDeptDO::getStatus,
                        SystemDeptDO::getSort,
                        SystemDeptDO::getLeaderUserId,
                        SystemDeptDO::getPhone,
                        SystemDeptDO::getEmail
                ));

        return deptConverter.entity2Form(entity);
    }

    @Override
    public List<Long> selectDeptChildrenIds(Long deptId) {
        // 获得部门下所有子部门
        List<SystemDeptDO> deptList = this.list(new LambdaQueryWrapper<SystemDeptDO>().like(SystemDeptDO::getTreePath, deptId).or().eq(SystemDeptDO::getId, deptId).select(SystemDeptDO::getId));
        return CollUtil.newArrayList(deptList.stream().map(SystemDeptDO::getId).collect(Collectors.toSet()));
    }

    @Override
    @CacheEvict(cacheNames = RedisKeyConstants.DEPT_CHILDREN_ID_LIST,
            allEntries = true) // allEntries 清空所有缓存，因为操作一个部门，涉及到多个缓存
    public Long createDept(DeptForm formData) {
        if (formData.getParentId() == null) {
            formData.setParentId(DDConstant.ROOT_NODE_ID);
        }
        // 校验父部门的有效性
        validateParentDept(null, formData.getParentId());
        // 校验部门名的唯一性
        validateDeptNameUnique(null, formData.getParentId(), formData.getName());

        SystemDeptDO entity = deptConverter.form2Entity(formData);
        // 部门路径
        String treePath = generateDeptTreePath(formData.getParentId());
        entity.setTreePath(treePath);
        // 保存部门并返回部门ID
        this.save(entity);
        return entity.getId();
    }

    @Override
    @CacheEvict(cacheNames = RedisKeyConstants.DEPT_CHILDREN_ID_LIST,
            allEntries = true) // allEntries 清空所有缓存，因为操作一个部门，涉及到多个缓存
    public Long updateDept(Long deptId, DeptForm formData) {
        if (formData.getParentId() == null) {
            formData.setParentId(DDConstant.ROOT_NODE_ID);
        }
        // 校验自己存在
        validateDeptExists(formData.getId());
        // 校验父部门的有效性
        validateParentDept(formData.getId(), formData.getParentId());
        // 校验部门名的唯一性
        validateDeptNameUnique(formData.getId(), formData.getParentId(), formData.getName());

        // form->entity
        SystemDeptDO entity = deptConverter.form2Entity(formData);
        entity.setId(deptId);
        // 部门路径
        String treePath = generateDeptTreePath(formData.getParentId());
        entity.setTreePath(treePath);
        // 保存部门并返回部门ID
        this.updateById(entity);
        return entity.getId();
    }

    /**
     * 部门 路径生成
     *
     * @param parentId 父ID
     * @return 父节点路径以英文逗号(, )分割，eg: 1,2,3
     */
    private String generateDeptTreePath(Long parentId) {
        String treePath = null;
        if (ObjectUtil.equals(DDConstant.ROOT_NODE_ID, parentId)) {
            treePath = String.valueOf(parentId);
        } else {
            SystemDeptDO parent = this.getById(parentId);
            if (parent != null) {
                treePath = parent.getTreePath() + StrUtil.COMMA + parent.getId();
            }
        }
        return treePath;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    @CacheEvict(cacheNames = RedisKeyConstants.DEPT_CHILDREN_ID_LIST,
            allEntries = true) // allEntries 清空所有缓存，因为操作一个部门，涉及到多个缓存
    public boolean deleteByIds(String ids) {
        long[] depts = StrUtil.splitToLong(ids, StrUtil.COMMA);
        for (Long id : depts) {
            // 校验是否存在
            validateDeptExists(id);
            // 校验是否有子部门
            if (this.baseMapper.selectCountByParentId(id) > 0) {
                throw exception(DEPT_EXITS_CHILDREN);
            }
            // 删除部门
            this.baseMapper.deleteById(id);
        }
        return true;
    }

    @VisibleForTesting
    void validateDeptExists(Long id) {
        if (id == null) {
            return;
        }
        SystemDeptDO dept = this.baseMapper.selectById(id);
        if (dept == null) {
            throw exception(DEPT_NOT_FOUND);
        }
    }

    @VisibleForTesting
    void validateParentDept(Long id, Long parentId) {
        if (parentId == null || ObjectUtil.equals(DDConstant.ROOT_NODE_ID, parentId)) {
            return;
        }
        // 1. 不能设置自己为父部门
        if (Objects.equals(id, parentId)) {
            throw exception(DEPT_PARENT_ERROR);
        }
        // 2. 父部门不存在
        SystemDeptDO parentDept = this.baseMapper.selectById(parentId);
        if (parentDept == null) {
            throw exception(DEPT_PARENT_NOT_EXITS);
        }
        // 3. 递归校验父部门，如果父部门是自己的子部门，则报错，避免形成环路
        if (id == null) { // id 为空，说明新增，不需要考虑环路
            return;
        }
        for (int i = 0; i < Short.MAX_VALUE; i++) {
            // 3.1 校验环路
            parentId = parentDept.getParentId();
            if (Objects.equals(id, parentId)) {
                throw exception(DEPT_PARENT_IS_CHILD);
            }
            // 3.2 继续递归下一级父部门
            if (parentId == null || ObjectUtil.equals(DDConstant.ROOT_NODE_ID, parentId)) {
                break;
            }
            parentDept = this.baseMapper.selectById(parentId);
            if (parentDept == null) {
                break;
            }
        }
    }

    @VisibleForTesting
    void validateDeptNameUnique(Long id, Long parentId, String name) {
        SystemDeptDO dept = this.baseMapper.selectByParentIdAndName(parentId, name);
        if (dept == null) {
            return;
        }
        // 如果 id 为空，说明不用比较是否为相同 id 的部门
        if (id == null) {
            throw exception(DEPT_NAME_DUPLICATE);
        }
        if (ObjectUtil.notEqual(dept.getId(), id)) {
            throw exception(DEPT_NAME_DUPLICATE);
        }
    }
}
